using System;
using System.Collections;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Text;
using System.Web;
using MediaWikiEngine.Language;

namespace MediaWikiEngine.Domain
{
	/// <summary>
	/// Summary description for WikiConnection.
	/// </summary>
	public class WikiConnection
	{
        /// <summary>
        /// The user agent this wiki connection should advertise when connecting to the wiki.
        /// </summary>
        public static string UserAgent = "MediaWikiEngine/1.0";

		string _wikiHost;
		CookieCollection _cookies = new CookieCollection();
		RequestLimiter _requestLimit = new RequestLimiter();
        WikiEndpoint _endpointData;
        bool _useSecureConnection = false;

        /// <summary>
        /// Gets the set of HTTP Cookies tracked by this connection.
        /// </summary>
        public CookieCollection Cookies
        {
            get { return _cookies; }
        }

        /// <summary>
        /// Gets or sets a flag indicating whether to use an HTTPS connection.  
        /// Will use an HTTP connection if false, which is the default.
        /// </summary>
        public bool UseSecureConnection
        {
            get { return _useSecureConnection; }
            set { _useSecureConnection = value; }
        }

        /// <summary>
        /// Gets or sets the current HTTP request limiter.  The limiter enforces a minimum time difference between all 
        /// requests to the wiki.
        /// </summary>
		public RequestLimiter RequestLimit
		{
			get { return _requestLimit; }
			set { _requestLimit = value; }
		}

        /// <summary>
        /// Creates a new wiki connection.
        /// </summary>
        /// <param name="wikiHost">The hostname or IP address of the wiki for this connection.</param>
        public WikiConnection(string wikiHost)
        {
            _wikiHost = wikiHost;

            _endpointData = new WikiEndpoint(this);
        }

        /// <summary>
        /// Creates a new wiki connection, and immediately sets a different <see cref="RequestLimit"/>.
        /// </summary>
        /// <param name="wikiHost">The hostname or IP address of the wiki for this connection.</param>
        /// <param name="limiter">The new <see cref="RequestLimiter"/> to use for this connection.</param>
        public WikiConnection(string wikiHost, RequestLimiter limiter) : this(wikiHost)
        {
            _requestLimit = limiter;
        }

        /// <summary>
        /// Information about how to reach various pages on a MediaWiki installation.
        /// </summary>
		public class WikiEndpoint
		{
            static string _baseAddress = "/wiki/index.php";

            /// <summary>
            /// Gets or sets the base address of the wiki; the default is "/wiki/index.php".
            /// </summary>
			public static string BaseAddress { get { return _baseAddress; } set { _baseAddress = value; } }

            public static string SubmitFormat { get { return BaseAddress + "?title={0}&action=submit"; } }
			public static string GetFormat { get { return BaseAddress + "?title={0}&redirect=no"; } }
			public static string GetWithNamespaceFormat { get { return BaseAddress + "?title={0}:{1}&redirect=no"; } }
			public static string GetNamespaceModeFormat { get { return BaseAddress + "?title={0}{1}&action={2}&redirect=no"; } }
			public static string ReferencesFormat { get { return BaseAddress + "?title={0}:Whatlinkshere&target={1}&limit={2}"; } }
			public static string UploadFormat { get { return BaseAddress + "?title={0}:Upload"; } }
			public static string AllMessagesFormat { get { return BaseAddress + "?title={0}:Allmessages&ot=html"; } }
			public static string LoginPageFormat { get { return BaseAddress + "?title={0}:Userlogin"; } }
			public static string LoginSubmitFormat { get { return BaseAddress + "?title={0}:Userlogin&action=submit"; } }
			public static string MoveFormat { get { return BaseAddress + "?title={0}:Movepage&action=submit"; } }
			public static string ExportFormat { get { return BaseAddress + "?title={0}:Export"; } }

			WikiConnection _conn;

			public WikiEndpoint(WikiConnection conn) 
			{ 
				_conn = conn; 
			}

			public string UploadPage()
			{
				return string.Format(UploadFormat, _conn.GetNamespace("Special"));
			}

			public string LoginPage()
			{
				return string.Format(LoginPageFormat, _conn.GetNamespace("Special"));
			}

			public string LoginSubmit()
			{
				return string.Format(LoginSubmitFormat, _conn.GetNamespace("Special"));
			}

			public string Get(string page)
			{
				return string.Format(GetFormat, page);
			}

			public string GetWithNamespace(string nameSpace, string page)
			{
				return string.Format(GetWithNamespaceFormat, _conn.GetNamespace(nameSpace), page);
			}

            /// <summary>
            /// Gets the full relative URL for a page in a particular action mode.
            /// </summary>
            /// <param name="nameSpace">The English or local namespace name for the page.</param>
            /// <param name="page">The title of the page.</param>
            /// <param name="mode">The action mode (i.e. edit, watch, submit, etc)</param>
			public string PageWithMode(string nameSpace, string page, string mode)
			{
				string realNamespace = _conn.GetNamespace(nameSpace);
				if (realNamespace.Length > 0)
					realNamespace += ":";
				return string.Format(GetNamespaceModeFormat, realNamespace, page, mode);
			}
		}

        /// <summary>
        /// Gets the wiki endpoint configuration, including the current base address.
        /// </summary>
		public WikiEndpoint Endpoint
		{
			get { return _endpointData; }
		}

        /// <summary>
        /// Performs an HTTP POST for an application/x-www-form-urlencoded form.
        /// </summary>
        /// <param name="path">The relative URL for the request.</param>
        /// <param name="formData">Form data being submitted to the web server.</param>
		public string DoPost(string path, NameValueCollection formData)
		{
			_requestLimit.WaitForTimeout();

			HttpWebRequest wr = (HttpWebRequest) HttpWebRequest.Create(GetCompleteUrl(path));
			wr.ContentType = "application/x-www-form-urlencoded";
			wr.Method = "POST";
            wr.UserAgent = UserAgent;
			wr.CookieContainer = new CookieContainer();
			wr.CookieContainer.Add(_cookies);

			Stream requestStream = wr.GetRequestStream();
			StreamWriter requestWriter = new StreamWriter(requestStream);
			string serializedData = FormSerialize(formData);
			requestWriter.Write(serializedData);
			requestWriter.Close();

			HttpWebResponse response = (HttpWebResponse) wr.GetResponse();
			_cookies.Add(response.Cookies);
			
			Stream responseStream = response.GetResponseStream();
			StreamReader responseReader = new StreamReader(responseStream);
			string responseData = responseReader.ReadToEnd();
			responseReader.Close();
			
			return responseData;
		}

        /// <summary>
        /// Performs an HTTP POST for a multipart/form-data form.
        /// </summary>
        /// <param name="path">The relative URL for the request.</param>
        /// <param name="formData">Form data being submitted to the web server.</param>
		public string DoPostForm(string path, NameValueCollection formData)
		{
			_requestLimit.WaitForTimeout();

			Guid boundary = Guid.NewGuid();

			HttpWebRequest wr = (HttpWebRequest) HttpWebRequest.Create(GetCompleteUrl(path));
			wr.ContentType = string.Format("multipart/form-data; boundary={0}", boundary.ToString("N"));
			wr.Method = "POST";
            wr.UserAgent = UserAgent;
			wr.CookieContainer = new CookieContainer();
			wr.CookieContainer.Add(_cookies);

			Stream requestStream = wr.GetRequestStream();
			StreamWriter requestWriter = new StreamWriter(requestStream);


			string serializedData = MultipartFormSerialize(formData, boundary);
			requestWriter.Write(serializedData);
			requestWriter.Close();

			HttpWebResponse response = (HttpWebResponse) wr.GetResponse();
			_cookies.Add(response.Cookies);
			
			Stream responseStream = response.GetResponseStream();
			StreamReader responseReader = new StreamReader(responseStream);
			string responseData = responseReader.ReadToEnd();
			responseReader.Close();
			
			return responseData;
		}

        /// <summary>
        /// Performs an HTTP POST for a multipart/form-data form when at least one of the form parameters is binary.
        /// </summary>
        /// <param name="path">The relative URL for the request.</param>
        /// <param name="formData">Form data being submitted to the web server.</param>
		public string DoPostForm(string path, NameObjectCollection formData)
		{
			_requestLimit.WaitForTimeout();

			Guid boundary = Guid.NewGuid();

			HttpWebRequest wr = (HttpWebRequest) HttpWebRequest.Create(GetCompleteUrl(path));
			wr.ContentType = string.Format("multipart/form-data; boundary={0}", boundary.ToString("N"));
			wr.Method = "POST";
			wr.UserAgent = UserAgent;
			wr.CookieContainer = new CookieContainer();
			wr.CookieContainer.Add(_cookies);

			Stream requestStream = wr.GetRequestStream();

			MultipartFormSerializeToStream(formData, boundary, requestStream);

			HttpWebResponse response = (HttpWebResponse) wr.GetResponse();
			_cookies.Add(response.Cookies);
			
			Stream responseStream = response.GetResponseStream();
			StreamReader responseReader = new StreamReader(responseStream);
			string responseData = responseReader.ReadToEnd();
			responseReader.Close();
			
			return responseData;
		}

        /// <summary>
        /// Performs an HTTP GET.
        /// </summary>
        /// <param name="path">The relative URL for the request.</param>
		public string DoGet(string path)
		{
			_requestLimit.WaitForTimeout();

			HttpWebRequest wr = (HttpWebRequest) HttpWebRequest.Create(GetCompleteUrl(path));
			wr.Method = "GET";
			wr.UserAgent = UserAgent;
			wr.CookieContainer = new CookieContainer();
			wr.CookieContainer.Add(_cookies);

			HttpWebResponse response = (HttpWebResponse) wr.GetResponse();
			_cookies.Add(response.Cookies);
			
			Stream responseStream = response.GetResponseStream();
			StreamReader responseReader = new StreamReader(responseStream);
			string responseData = responseReader.ReadToEnd();
			responseReader.Close();
			
			return responseData;
		}

        /// <summary>
        /// Performs an HTTP GET of a wiki page.
        /// </summary>
        /// <param name="nameSpace">The English or local namespace for the page.</param>
        /// <param name="page">The title of the page.</param>
		public string DoGet(string nameSpace, string page)
		{
			string finalPath;
			if (nameSpace.Length < 1)
				finalPath = Endpoint.Get(page);
			else
				finalPath = Endpoint.GetWithNamespace(nameSpace, page);

            return DoGet(finalPath);
		}

		private string GetCompleteUrl(string path)
		{
			return string.Format("http{0}://{1}{2}", _useSecureConnection ? "s" : "", _wikiHost, path);
		}

		private string GetNamespace(string namespaceDescription)
		{
			try
			{
				return LanguageProvider.Instance.Namespaces[namespaceDescription];
			}
			catch 
			{
				return string.Empty;
			}
		}

		private string FormSerialize(NameValueCollection data)
		{
			StringBuilder sb = new StringBuilder();

			bool first = true;

			foreach (string key in data.Keys)
			{
				if (false == first)
				{
					sb.Append("&");
				}
				else
				{
					first = false;
				}

				sb.AppendFormat("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(data[key]));
			}

			return sb.ToString();

		}

		private string MultipartFormSerialize(NameValueCollection data, Guid boundary)
		{
			StringBuilder sb = new StringBuilder();

			foreach (string key in data.Keys)
			{
				sb.AppendFormat("\r\n--{0}\r\nContent-disposition: form-data; name=\"{1}\"\r\n\r\n{2}", boundary.ToString("N"), key, data[key]);
			}

			sb.AppendFormat("\r\n--{0}--", boundary.ToString("N"));

			return sb.ToString();
		}

		private void MultipartFormSerializeToStream(NameObjectCollection data, Guid boundary, Stream s)
		{
			BinaryWriter bw = new BinaryWriter(s);

			foreach (string key in data.Keys)
			{
				object currentObj = data[key];
				if (currentObj is FileDescription)
				{
					FileDescription img = currentObj as FileDescription;
                    string line = string.Format("\r\n--{0}\r\nContent-disposition: form-data; name=\"{1}\"; filename=\"{2}\"\r\nContent-type: {3}\r\n\r\n", boundary.ToString("N"), key, img.Filename, img.Mimetype);
                    byte[] buffer = UTF8Encoding.UTF8.GetBytes(line);
                    bw.Write(buffer, 0, buffer.Length);
					bw.Write(img.Data);
				}
				else
				{
                    string line = string.Format("\r\n--{0}\r\nContent-disposition: form-data; name=\"{1}\"\r\n\r\n{2}", boundary.ToString("N"), key, currentObj.ToString());
                    byte[] buffer = UTF8Encoding.UTF8.GetBytes(line);
                    bw.Write(buffer, 0, buffer.Length);
				}
			}

            string trailer = string.Format("\r\n--{0}--\r\n", boundary.ToString("N"));
            byte[] trailerBuffer = UTF8Encoding.UTF8.GetBytes(trailer);
            bw.Write(trailerBuffer, 0, trailerBuffer.Length);

            bw.Close();
		}

		

	}
}
